﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.IO;
using System.Collections.Generic;

namespace Pharmacy.Ultilities
{
    /// <summary>
    /// Provides support for IO access.
    /// </summary>
    /// <author>ThangTran</author>
    /// <history>
    /// 2011.02.15 - ThangTran: splited from external library MHTools.IO and added to DIoms.Utilities.
    /// </history>
    public static class IOHelper
    {
        /// <summary>
        /// Gets or sets the custom execute path which is used to redirect the directory usage in testing scenario.
        /// </summary>
        public static string CustomExecutePath { get; set; }

        /// <summary>
        /// Returns the path that current assembly running in.
        /// If the CustomExecutePath is not null, it will be returned instead.
        /// </summary>
        public static string ExecutePath
        {
            get
            {
                return CustomExecutePath ?? Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            }
        }

        /// <summary>
        /// Combines 2 or many paths together.
        /// </summary>
        public static string CombinePath(params string[] paths)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(paths != null && paths.Length > 0);
            // --- End parameters checking code -------------------------------

            return paths.Aggregate("", Path.Combine);
        }
        /// <summary>
        /// Prepares a folder with sub path.
        /// The SubPath may be folder name or file name.
        /// </summary>
        /// <param name="path">Parent folder.</param>
        /// <param name="subPath">Sub folder or file name.</param>
        /// <returns>Indices the folder and its sub are created successfully or not.</returns>
        public static bool PrepareFolder(string path, string subPath)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(path != null);
            // --- End parameters checking code -------------------------------

            try
            {
                //get absoluted path of Path parameter
                string absolutedPath = Path.GetFullPath(path);
                //add '/' to the end of absoluted path, if needed
                if (absolutedPath[absolutedPath.Length - 1] != '\\') absolutedPath += '\\';
                //remove '/' at the begini of the SubPath, if any
                while (subPath != "" && (subPath[0] == '/' || subPath[0] == '\\')) subPath = subPath.Remove(0, 1);
                //merge 2 string together
                absolutedPath += subPath;
                //remove filename, if any
                absolutedPath = Path.GetDirectoryName(absolutedPath);
                Debug.Assert(absolutedPath != null);
                //create folder
                Directory.CreateDirectory(absolutedPath);
            }
            catch (Exception)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Prepares a folder with sub path.
        /// The folderPath must be file name.
        /// </summary>
        public static void PrepareFolder(string folderPath)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(folderPath != null);
            // --- End parameters checking code -------------------------------

            Directory.CreateDirectory(folderPath);
        }

        /// <summary>
        /// Prepares a folder with sub path.
        /// The filePath must be file name.
        /// </summary>
        public static void PrepareFolderContainsFile(string filePath)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(filePath != null);
            // --- End parameters checking code -------------------------------

            PrepareFolder(Path.GetDirectoryName(filePath));
        }

        /// <summary>
        /// Serializes the object as XML and saves it to specified path.
        /// </summary>
        /// <returns>Indicates the serialization process succeed or not.</returns>
        public static void SerializeAsXML(object obj, string path)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(obj != null);
            Debug.Assert(path != null);
            // --- End parameters checking code -------------------------------

            FileStream file = null;

            try
            {
                //create serializer
                System.Xml.Serialization.XmlSerializer xml = new System.Xml.Serialization.XmlSerializer(obj.GetType());
                //create file stream
                file = new FileStream(path, FileMode.Create, FileAccess.Write);
                //save object to file stream
                xml.Serialize(file, obj);
            }
            finally
            {
                if (file != null)
                {
                    //finalize
                    file.Flush();
                    file.Close();
                }
            }
        }

        /// <summary>
        /// Reads specified file as XML and deserializes it to an object.
        /// </summary>
        /// <returns>Indicates the deserialization process succeed or not.</returns>
        // public static bool DeserializeAsXML(out object obj, string path)
        public static void DeserializeAsXML<T>(out T obj, string path)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(path != null);
            // --- End parameters checking code -------------------------------

            FileStream file = null;
            obj = default(T);
            try
            {
                //create serializer
                System.Xml.Serialization.XmlSerializer xml = new System.Xml.Serialization.XmlSerializer(typeof(T));
                //create file stream
                file = new FileStream(path, FileMode.Open, FileAccess.Read);
                //load object from file stream
                obj = (T)xml.Deserialize(file);
            }
            finally
            {
                if (file != null)
                {
                    //finalize
                    file.Flush();
                    file.Close();
                }
            }
        }

        /// <summary>
        /// Validates an Windows file path.
        /// </summary>
        /// <param name="filePath">File path to validate.</param> 
        /// <returns>Returns false if the path is null, or empty, or longer than the system-defined maximum length,
        /// or contains any invalid char. Otherwise, true.</returns>
        /// <author>hungle@dicentral.com</author>
        /// <history>
        /// 2011.02.15 - hungle@dicentral.com: added.
        /// 2011.04.27 - ThangTran: moved "return false;" from outside try-catch to inside catch to satisfy Resharper "Empty try-catch" rule.
        /// </history>
        public static bool ValidateWindowsFilePath(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                return false;
            }

            try
            {
                //Validates the directory.
                string fileName = Path.GetFileName(filePath);

                if (string.IsNullOrEmpty(fileName)) return false;

                List<char> invalidChars = new List<char>(Path.GetInvalidFileNameChars());

                //Validates the file name.
                return fileName.All(c => !invalidChars.Contains(c));
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Validates an Windows directory path.
        /// </summary>
        /// <param name="dirPath">Directory path to validate.</param> 
        /// <returns>Returns false if the path is null, or empty, or longer than the system-defined maximum length,
        /// or contains any invalid char. Otherwise, true.</returns>
        /// <author>hungle@dicentral.com</author>
        /// <history>
        /// 2011.02.15 - hungle@dicentral.com: added.
        /// 2011.04.27 - ThangTran: moved "return false;" from outside try-catch to inside catch to satisfy Resharper "Empty try-catch" rule.
        /// </history>
        public static bool ValidateWindowsDirectoryPath(string dirPath)
        {
            if (string.IsNullOrEmpty(dirPath))
            { return false; }

            try
            {
                //Validates the directory.
                //string dirName = Path.GetDirectoryName(dirPath);
                Path.GetDirectoryName(dirPath);

                //if (string.IsNullOrEmpty(dirName)) return false;

                //List<char> invalidChars = new List<char>(Path.GetInvalidPathChars());

                ////Validates the directory name.
                //foreach (char c in dirName)
                //{
                //    if (invalidChars.Contains(c)) return false;
                //}

                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Gets full name of a directory. If the dirPath is a relative path, then combines it with the executing assembly directory.
        /// </summary>
        /// <param name="dirPath">Directory path get full name.</param> 
        /// <returns>Returns full name of a directory.</returns>
        /// <exception cref="T:System.IO.IOException">
        ///     <paramref name="dirPath"/> is null, or empty, or longer than the
        ///     system-defined maximum length, or contains any invalid char.</exception>        
        /// <author>hungle@dicentral.com</author>
        /// <history>
        /// 2011.02.15 - hungle@dicentral.com: added.
        /// </history>
        public static string GetFullDirectoryName(string dirPath)
        {
            if (!ValidateWindowsDirectoryPath(dirPath))
            {
                throw new IOException(string.Format("Invalid Directory Path: [{0}]", dirPath));
            }

            DirectoryInfo di = new DirectoryInfo(dirPath);

            return dirPath.IndexOf(di.FullName) == 0 ? di.FullName : CombinePath(ExecutePath, dirPath);
        }
    }
}